/** @Author: yeganaaa@163.com مۇختەرجان مەخمۇت muhtarjan mahmood
@Date: 2020-06-13 11:53:20
 * @Last Modified by: yeganaaa@163.com مۇختەرجان مەخمۇت muhtarjan mahmood
 * @Last Modified time: 2020-06-13 11:54:24
*/
import {Completer, Fragment, RpcClient, State} from "dream_rpc";
import {
    Channels,
    IMessageBusClientInterface,
    IMessageBusServerInterface,
    VirtualChannel
} from "../Shared/Interfaces";
import {ChannelMappingValue, MessageBusServer, WorkMode} from "./MessageBusServer";

export class MessageBusClient extends RpcClient<IMessageBusServerInterface, IMessageBusClientInterface, MessageBusClientDelegateState>{
    public channelsOfThisClient: string[] = []
    private implementation: IMessageBusServerInterface = new MessageBusClientInterfaceImplementation(this)
    constructor() {
        super();
    }
    public async createState(): Promise<MessageBusClientDelegateState> {
        return new MessageBusClientDelegateState()
    }

    public async getLocalInterfaceImplementation(): Promise<IMessageBusServerInterface> {
        return this.implementation
    }

    protected async onDisconnected() {
        const server = this.server as MessageBusServer
        for (const value of this.channelsOfThisClient) {
            const mapping = server.channelClientMappings.get(value)!
            this.implementation.close(new VirtualChannel(value))
            if (mapping.mode == WorkMode.Master) server.channelClientMappings.delete(value)
        }
    }
}
export class MessageBusClientDelegateState extends State {
    
}
export class MessageBusClientInterfaceImplementation implements IMessageBusServerInterface {
    private textDecoder = new TextDecoder()
    private textEncoder = new TextEncoder()
    constructor(private _client: MessageBusClient) {

    }
    public async close(localChannel: VirtualChannel): Promise<void> {
        const server = (this._client.server as MessageBusServer)

        const mappingsOfThisChannel = server.channelClientMappings.get(localChannel.name)
        if (mappingsOfThisChannel == null) {
            //TAG HERE
            // throw new Error(`This channel "${localChannel.name}" is already closed.`)
            return
        }
        if (mappingsOfThisChannel.clientId != this._client.id) throw new Error(`You cannot close channels that under other message bus connection, this channel '${localChannel.name}' is not you are created.`)
        mappingsOfThisChannel.connectedChannels.forEach(async partnerChannel => {
            const mappingsOfPartnerChannel = server.channelClientMappings.get(partnerChannel)!
            if (mappingsOfThisChannel == null) {
                throw new Error(`Other partner channel '${partnerChannel}' is not exists on the server.`)
            }
            mappingsOfPartnerChannel.connectedChannels = mappingsOfPartnerChannel.connectedChannels.filter(item => item != localChannel.name) //Remove this channel (local channel) from partner connection's list.
            const partnerClient = this._client.server.clients.get(mappingsOfPartnerChannel.clientId)!
            if (mappingsOfPartnerChannel.mode == WorkMode.Slave) await partnerClient.bridge.localInterface.close(new VirtualChannel(partnerChannel))
            partnerClient.bridge.remoteInterface.onChannelClosed({ remote: localChannel, local: new VirtualChannel(partnerChannel) }) //And fire channel closed event on partner client
        })
        if (mappingsOfThisChannel.mode == WorkMode.Slave) server.channelClientMappings.delete(localChannel.name)
        this._client.channelsOfThisClient = this._client.channelsOfThisClient.filter(item => item != localChannel.name) //Remove this channel from list of it's connection
    }
    public async sendData(bytes: Uint8Array): Promise<void> {
        const server = (this._client.server as MessageBusServer)

        const receivedFragments = [...Fragment.fromBytes(bytes)]
        const channels = JSON.parse(this.textDecoder.decode(receivedFragments[0].data)) as Channels
        const remoteClient = this._client.server.clients.get(server.channelClientMappings.get(channels.remote.name)?.clientId ?? "empty")
        if (remoteClient == null) throw new Error(`Remote channel "${channels.remote.name}" is not found.`)
        const encoded = Fragment.encodeAll(
            new Fragment(this.textEncoder.encode(JSON.stringify({ local: channels.remote, remote: channels.local }))),
            receivedFragments[1]
        )
        // console.log(`From ${channels.local.name} --> ${channels.remote.name}, data length: ${receivedFragments[1].data.length}`)
        await remoteClient.bridge.remoteInterface.onDataReceived(encoded)
    }
    public async create(channel: VirtualChannel): Promise<void> {
        const server = (this._client.server as MessageBusServer)
        let exists = server.channelClientMappings.has(channel.name)
        if (exists) {
            throw new Error(`channel address "${channel.name}" is already in use.`)
        }
        server.channelClientMappings.set(channel.name, new ChannelMappingValue([],  this._client.id, WorkMode.Master))
        this._client.channelsOfThisClient.push(channel.name)
    }
    public async connect(channels: Channels): Promise<void> {
        const server = (this._client.server as MessageBusServer)

        let selfMapping = server.channelClientMappings.get(channels.local.name)
        if (selfMapping != null) throw new Error(`This channel "${channels.local.name}" is already in bind to other channels.`)
        let otherMapping = server.channelClientMappings.get(channels.remote.name)
        if (otherMapping == null) throw new Error(`Remote channel "${channels.remote.name}" is not exists.`)
        const destinationClient = this._client.server.clients.get(otherMapping.clientId)
        if (destinationClient == null) throw new Error("Destination channel _client is not found.")
        selfMapping = new ChannelMappingValue([channels.remote.name], this._client.id)
        otherMapping.connectedChannels.push(channels.local.name)
        server.channelClientMappings.set(channels.local.name, selfMapping)
        this._client.channelsOfThisClient.push(channels.local.name)
        await destinationClient.bridge.remoteInterface.onChannelConnected({
            local: channels.remote,
            remote: channels.local
        })
        // console.log("After onClientConnected")
    }
}
